package com.hqd.jjvm.rtda.heap.classloader;

import com.hqd.jjvm.classformat.ClassFile;
import com.hqd.jjvm.classformat.FieldInfo;
import com.hqd.jjvm.classformat.JClassChecker;
import com.hqd.jjvm.classformat.MethodInfo;
import com.hqd.jjvm.classformat.constantpool.ConstantPool;
import com.hqd.jjvm.classpath.ClassParse;
import com.hqd.jjvm.instructions.InstructionContext;
import com.hqd.jjvm.rtda.heap.JClass;
import com.hqd.jjvm.rtda.heap.JField;
import com.hqd.jjvm.rtda.heap.JMethod;
import com.hqd.jjvm.rtda.heap.RuntimeConstantPool;
import com.hqd.jjvm.rtda.heap.accesss.CommonAccessFlag;
import com.hqd.jjvm.rtda.heap.ref.JClassRef;
import com.hqd.jjvm.rtda.heap.state.JClassState;
import com.hqd.jjvm.rtda.memory.JMethodArea;
import com.hqd.jjvm.rtda.obj.JObject;
import com.hqd.jjvm.rtda.slot.NumberSlot;
import com.hqd.jjvm.rtda.slot.RefSlot;
import com.hqd.jjvm.rtda.slot.Slot;
import com.hqd.jjvm.util.ClassUtil;
import lombok.Getter;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

/**
 * 类加载器
 *
 * @author hqd
 */
@Getter
public abstract class JClassLoader {

    private final JClassLoader parentJClassLoader;
    private final ClassParse classParse;
    protected static final String JAVA_HOME_PATH = System.getProperty("java.home");
    protected static final JClassChecker classChecker = new JClassChecker();

    public JClassLoader(JClassLoader parentJClassLoader, ClassParse classParse) {
        this.parentJClassLoader = parentJClassLoader;
        this.classParse = classParse;
        this.initLoad();
    }

    public JClassLoader(ClassParse classParse) {
        this(null, classParse);
    }

    protected void initLoad() {
        try {
            JClass jClass = this.loadJClass(Class.class.getName());
            jClass.setJObject(JMethodArea.getJClass(Class.class.getName()).newJObject());
            jClass = this.loadJClass(Object.class.getName());
            jClass.setJObject(JMethodArea.getJClass(Class.class.getName()).newJObject());
            this.loadJClass(String.class.getName());

            loadPrimitiveClass("float");
            loadPrimitiveClass("int");
            loadPrimitiveClass("double");
            loadPrimitiveClass("long");
            loadPrimitiveClass("byte");
            loadPrimitiveClass("boolean");
            loadPrimitiveClass("char");
            loadPrimitiveClass("short");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private JClass loadPrimitiveClass(String className) {
        JClass jClass = new JClass();
        jClass.setAccessFlags(CommonAccessFlag.ACC_PUBLIC.getHex());
        jClass.setLoader(this);
        jClass.setState(JClassState.FULLY_INITIALIZED);
        jClass.setClassName(className);
        bindingJClassObj(jClass);
        JMethodArea.putJClass(jClass);
        return JMethodArea.getJClass(className);
    }

    private void bindingJClassObj(JClass jClass) {
        if (JMethodArea.getJClass("java/lang/Class") != null) {
            JObject jObject = jClass.getJObject();
            if (jObject == null) {
                jObject = JMethodArea.getJClass("java/lang/Class").newJObject();
            }
            jObject.setExtra(jClass);
            jClass.setJObject(jObject);
        }
    }

    /**
     * 加载非数组类
     *
     * @param className
     * @return
     */
    private JClass loadNoArrayClass(String className) throws ClassNotFoundException, IOException {
        JClass jClass = JMethodArea.getJClass(className);
        if (jClass == null) {
            jClass = defineJClass(className);
            link(jClass);
            jClass.setState(JClassState.LINKED);
        }
        return jClass;
    }

    /**
     * 加载数组类
     * 数据访问控制：基础类型为public，引用类型和引用一致
     *
     * @param className
     * @return
     */
    protected JClass loadArrayClass(String className) throws ClassNotFoundException, IOException {
        if (JMethodArea.getJClass(className) == null) {
            JClass arrayJClass = new JClass();
            arrayJClass.setAccessFlags(CommonAccessFlag.ACC_PUBLIC.getHex());
            arrayJClass.setClassName(className);
            arrayJClass.setState(JClassState.FULLY_INITIALIZED);
            JClass superJClass = loadJClass("java/lang/Object");
            arrayJClass.setSuperClassName(superJClass.getClassName());
            arrayJClass.setSuperClass(superJClass);
            JClass clone = loadJClass("java/lang/Cloneable");
            JClass serializable = loadJClass("java/io/Serializable");
            arrayJClass.setInterfaceClasses(new JClass[]{clone, serializable});
            arrayJClass.setInterfaceNames(new String[]{clone.getClassName(), serializable.getClassName()});
            /**
             * 添加Object方法
             */
            arrayJClass.getVtableMap().putAll(superJClass.getVtableMap());
            arrayJClass.setJMethods(superJClass.getJMethods());
            JMethodArea.putJClass(arrayJClass);
        }

        return JMethodArea.getJClass(className);
    }

    /**
     * 加载类
     *
     * @param jclassName
     * @return
     * @throws ClassNotFoundException
     * @throws IOException
     */
    public JClass loadJClass(String jclassName) throws ClassNotFoundException, IOException {
        if (StringUtils.isNotBlank(jclassName)) {
            jclassName = jclassName.replaceAll("\\.", "/");
            JClass jClass = null;
            if (this.parentJClassLoader != null) {
                try {
                    jClass = parentJClassLoader.loadJClass(jclassName);
                } catch (ClassNotFoundException e) {
                    if (jclassName.startsWith("[")) {
                        jClass = loadArrayClass(jclassName);
                    } else {
                        jClass = loadNoArrayClass(jclassName);
                    }
                }
            } else {
                if (jclassName.startsWith("[")) {
                    jClass = loadArrayClass(jclassName);
                } else {
                    jClass = loadNoArrayClass(jclassName);
                }
            }
            bindingJClassObj(jClass);
            return jClass;
        }
        throw new ClassNotFoundException("没有找到" + jclassName + "类");
    }

    protected ClassFile findJClass(String jclassName) throws ClassNotFoundException {
        ClassFile classFile = null;
        try {
            if (getClassParse().isExists(jclassName)) {
                classFile = JMethodArea.getClassFile(jclassName);
                if (classFile == null) {
                    classFile = classParse.parseClass(jclassName);
                }
            } else {
                throw new ClassNotFoundException("没有找到" + jclassName + "类");
            }
        } catch (IOException e) {
            throw new ClassNotFoundException("没有找到" + jclassName + "类");
        }
        return classFile;
    }

    protected JClass defineJClass(String jclassName) throws ClassNotFoundException, IOException {
        if (JMethodArea.getJClass(jclassName) == null) {
            ClassFile classFile = findJClass(jclassName);
            JClass jClass = new JClass();
            jClass.setSourceFile(classFile.getClassFileInfo().getPath());
            jClass.setState(JClassState.ALLOCATED);
            jClass.setClassName(jclassName);
            jClass.setAccessFlags(classFile.getAccessFlags());
            jClass.setLoader(this);
            resolveConstants(classFile, jClass);
            resolveInterfaces(classFile, jClass);
            resolveSuperClass(classFile, jClass);
            resolveMethods(classFile, jClass);
            resolveFields(classFile, jClass);
            JMethodArea.putJClass(jClass);
            jClass.setState(JClassState.LOADED);
        }
        return JMethodArea.getJClass(jclassName);
    }

    /**
     * 准备
     *
     * @param jClass
     */
    protected void prepare(JClass jClass) {
        calcInstanceFieldSlotIds(jClass);
        calcStaticFieldSlotIds(jClass);
        //allocAndInitStaticVars(jClass);
    }

    /**
     * 链接
     */
    protected void link(JClass jClass) {
        classChecker.checkSyntax(jClass);
        prepare(jClass);
    }


    @Deprecated
    private void initStaticFinalVar(JClass jClass, JField jField) {
        Slot[] staticSlots = jClass.getStaticVars();
        List<Object> constantPoolList = jClass.getConstantPool().getConstants();
        Integer cpIndex = jField.getConstantIndex();
        if (cpIndex == null) {
            return;
        }
        Integer slotId = jField.getSlotId();
        if ("ZBCSI".contains(jField.getDescriptor())) {
            staticSlots[slotId] = new NumberSlot((Integer) constantPoolList.get(cpIndex));
        } else if ("J".equals(jField.getDescriptor())) {
            //staticSlots[slotId] = new NumberSlot((Long) constantPoolList.get(cpIndex));
        } else if ("F".equals(jField.getDescriptor())) {
            //staticSlots[slotId] = new NumberSlot((Float) constantPoolList.get(cpIndex));
        } else if ("D".equals(jField.getDescriptor())) {
            //staticSlots[slotId] = new NumberSlot((Double) constantPoolList.get(cpIndex));
        } else if (jField.getDescriptor().startsWith("[")) {
            //staticSlots[slotId] = new RefSlot(JObject.newJArray(jClass, (Integer) constantPoolList.get(cpIndex)));
        } else if ("Ljava/lang/String;".equals(jField.getDescriptor())) {

        }
    }

    /**
     * 计算静态属性值
     *
     * @param jClass
     */
    private void calcStaticFieldSlotIds(JClass jClass) {
        int slotId = 0;
        List<Slot> staticList = new LinkedList<>();
        for (JField jField : jClass.getJFields()) {
            if (jField.isStatic()) {
                jField.setSlotId(slotId++);
                /**
                 * long,double类型多加一个槽位
                 */
                if (ClassUtil.isDword(jField.getDescriptor())) {
                    slotId++;
                    if (ClassUtil.isDouble(jField.getDescriptor())) {
                        staticList.add(new NumberSlot(0D));
                    } else {
                        staticList.add(new NumberSlot(0L));
                    }
                    staticList.add(new NumberSlot());
                } else {
                    if (ClassUtil.isPrimitive(jField.getDescriptor())) {
                        if (ClassUtil.isFloat(jField.getDescriptor())) {
                            staticList.add(new NumberSlot(0F));
                        } else {
                            staticList.add(new NumberSlot(0));
                        }
                    } else {
                        staticList.add(new RefSlot());
                    }
                }
                JMethodArea.putStaticJField(jField);
            }
        }
        jClass.setStaticSlotCount(slotId);
        jClass.setStaticVars(staticList.toArray(new Slot[]{}));
    }

    /**
     * 计算属性值
     *
     * @param jClass
     */
    private int calcInstanceFieldSlotIds(JClass jClass) {
        int slotId = 0;
        if (jClass.getSuperClass() != null) {
            slotId += calcInstanceFieldSlotIds(jClass.getSuperClass());
        }
        for (JField jField : jClass.getJFields()) {
            if (!jField.isStatic()) {
                jField.setSlotId(slotId++);
                if (ClassUtil.isDword(jField.getDescriptor())) {
                    slotId++;
                }
            }
        }
        jClass.setInstanceSlotCount(slotId);
        return slotId;
    }

    /**
     * 解析属性
     *
     * @param classFile
     * @param jClass
     */
    private void resolveFields(ClassFile classFile, JClass jClass) {
        JField[] jFields = new JField[classFile.getFieldCount()];
        List<ConstantPool> constantPoolList = classFile.getConstantPools();
        for (int i = 0; i < jFields.length; i++) {
            FieldInfo fieldInfo = classFile.getFieldInfos().get(i);
            jFields[i] = new JField(fieldInfo, jClass, constantPoolList);
        }
        jClass.setJFields(jFields);
    }

    /**
     * 解析方法
     *
     * @param classFile
     * @param jClass
     */
    private void resolveMethods(ClassFile classFile, JClass jClass) {
        if (jClass.getSuperClass() != null) {
            JClass superJClass = jClass.getSuperClass();
            jClass.getVtableMap().putAll(superJClass.getVtableMap());
        }
        JMethod[] jMethods = new JMethod[classFile.getMethodCount()];
        List<ConstantPool> constantPoolList = classFile.getConstantPools();
        for (int i = 0; i < classFile.getMethodCount(); i++) {
            MethodInfo methodInfo = classFile.getMethodInfos().get(i);
            jMethods[i] = new JMethod(methodInfo, jClass, constantPoolList);
            if (!(jMethods[i].isPrivate() || jMethods[i].isStatic())) {
                if (!jMethods[i].getName().equals("<init>")) {
                    if (jMethods[i].isFinal()) {
                        if (jClass.getVtableMap().get(jMethods[i]) == null) {
                            jClass.getVtableMap().put(generateVtableKey(jClass, jMethods[i]), jMethods[i]);
                        }
                    } else {
                        jClass.getVtableMap().put(generateVtableKey(jClass, jMethods[i]), jMethods[i]);
                    }
                }
            }
        }
        jClass.setJMethods(jMethods);
    }

    /**
     * 生成虚方法表key
     *
     * @param jc
     * @param jMethod
     * @return
     */
    private String generateVtableKey(JClass jc, JMethod jMethod) {
        return jMethod.getName() + jMethod.getDescriptor();
    }

    /**
     * 解析父类
     *
     * @param classFile
     * @param jClass
     * @throws ClassNotFoundException
     */
    private void resolveSuperClass(ClassFile classFile, JClass jClass) throws ClassNotFoundException, IOException {
        String superClassName = parseClass(classFile, classFile.getSuperClassIndex());
        jClass.setSuperClassName(superClassName);
        if (StringUtils.isNotBlank(superClassName)) {
            JClass superClass = loadJClass(superClassName);
            jClass.setSuperClass(superClass);
        }
    }

    /**
     * 解析接口
     *
     * @param classFile
     * @param jClass
     * @throws ClassNotFoundException
     * @throws IOException
     */
    private void resolveInterfaces(ClassFile classFile, JClass jClass) throws ClassNotFoundException, IOException {
        String[] interfaceNames = new String[classFile.getInterfaceCount()];
        JClass[] interfaces = new JClass[classFile.getInterfaceCount()];
        for (int i = 0; i < classFile.getInterfaceCount(); i++) {
            interfaceNames[i] = classFile.getInterfaces().get(i).getVal().getClassName();
            interfaces[i] = loadJClass(interfaceNames[i]);

        }
        jClass.setInterfaceClasses(interfaces);
        jClass.setInterfaceNames(interfaceNames);
    }

    /**
     * 解析运行时常量池
     *
     * @param classFile
     * @param jClass
     */
    private void resolveConstants(ClassFile classFile, JClass jClass) {
        RuntimeConstantPool runtimeConstantPool = new RuntimeConstantPool(jClass, classFile.getConstantPools());
        jClass.setConstantPool(runtimeConstantPool);
    }

    private String parseClass(ClassFile classFile, int index) {
        if (index == 0) {
            return null;
        }
        List<ConstantPool> constantPoolList = classFile.getConstantPools();
        return ((JClassRef) constantPoolList.get(index).getVal()).getClassName();
    }

    /**
     * 解析当前类对象
     *
     * @param classFile
     * @return
     */
    private String resolveThisClass(ClassFile classFile) {
        return parseClass(classFile, classFile.getThisClassIndex());
    }

    public static void initVM(JClassLoader loader) throws IOException, ClassNotFoundException {
        JClass vmClass = loader.loadJClass("sun/misc/VM");
        vmClass.setState(JClassState.BEING_INITIALIZED);
        JMethod vm = vmClass.getJMethod("<clinit>", "()V");
        InstructionContext v = new InstructionContext(vm);
        v.invoke();
        vmClass.setState(JClassState.FULLY_INITIALIZED);
    }

    public static void main(String[] args) throws Exception {
        String userPath = "F:/萧瑟/呵呵/java/JavaEE/jjvm/target/test-classes";
        JClassLoader loader = new AppJClassLoader(userPath);
        initVM(loader);
        JClass mainClass = loader.loadJClass("com.hqd.test.JVMTest");
        JMethod method = mainClass.getMainMethod();
        InstructionContext instruction = new InstructionContext(method);
        instruction.invoke();
    }
}
